// 按照Makeblock的规则，2byte转3byte， 4byte转5byte等
function shiftBytes(value: number, length: number): number[] {
    let result: number[] = [];
    for(let i=0; i<length; i++) {
        result.unshift(value & 0x7f);
        value = value >> 7;
    }
    result.unshift(value & (0xff >> (8-length))); // add remaining carry
    return result;
}

export function byteTo2Bytes(value: number): number[] {
    return shiftBytes(value, 1);
}

export function shortTo3Bytes(value: number): number[] {
    return shiftBytes(value, 2);
}

export function SHORTTo2Bytes(value: number): number[] {
    let result = shiftBytes(value, 2);
    result.shift();
    return result;
}

export function longTo5Bytes(value: number): number[] {
    return shiftBytes(value, 4);
}

export function int16To2Bytes(value: number): number[] {
    return [(value >> 8) & 0xff, value & 0xff]
}

export function float32To4Bytes(value: number): number[] {
    let float32Array = new Float32Array([value]);
    let uint8Array = new Uint8Array(float32Array.buffer);
    return [uint8Array[3], uint8Array[2], uint8Array[1], uint8Array[0]];
}

export function int32To4Bytes(value: number): number[] {
    let int32Array = new Int32Array([value]);
    let uint8Array = new Uint8Array(int32Array.buffer);
    return [uint8Array[3], uint8Array[2], uint8Array[1], uint8Array[0]];
}

export function floatTo5Bytes(value: number): number[] {
    let float32Array = new Float32Array([value]);
    let uint32Array = new Uint32Array(float32Array.buffer);
    return shiftBytes(uint32Array[0], 4);
}

export function Uint8ArrayToArray(value: Uint8Array): number[] {
    let result = [];
    for(let i=0; i<value.length; i++) {
        result.push(value[i]);
    }
    return result;
}

export function UTF8StringToBytes(value: string): number[] {
    let encoder = new TextEncoder();
    return encoder.encode(value);
}

export function UTF8StringToBytesEndWith0(value: string): number[] {
    let result = UTF8StringToBytes(value);
    result.push(0);
    return result;
}

export function bytesToUTF8String(bytes: number[]): string {
    let decoder = new TextDecoder();
    return decoder.decode(bytes);
}

function unshiftBytes(bytes: number[]): number {
    let result = 0;
    let byte: number | undefined = 0;
    while((byte = bytes.shift()) !== undefined) {
        result = result + byte;
        if(bytes.length > 0) {
            result = result << 7;
        }
    }
    return result;
}

export function byte2ToByte(bytes: number[]): number {
    let uint8Bytes = unshiftBytes(bytes);
    let uint8Array = new Uint8Array([uint8Bytes]);  // add negative sign
    let int8Array = new Int8Array(uint8Array.buffer);
    return int8Array[0];
}

export function byte3ToShort(bytes: number[]): number {
    let uint8Bytes = unshiftBytes(bytes);
    let uint16Array = new Uint16Array([uint8Bytes]);    // add negative sign
    let int16Array = new Int16Array(uint16Array.buffer);
    return int16Array[0];
}

export function byte2ToShort(bytes: number[]): number {
    return unshiftBytes(bytes);
}

export function byte5ToLong(bytes: number[]): number {
    let uint8Bytes = unshiftBytes(bytes); 
    let uint32Array = new Uint32Array([uint8Bytes]);    // add negative sign
    let int32Array = new Int32Array(uint32Array.buffer); 
    return int32Array[0];
}

export function byte5ToFloat(bytes: number[]): number {
    let uint8Bytes = unshiftBytes(bytes);
    let uint32Array = new Uint32Array([uint8Bytes]);    // add negative sign
    let float32Array = new Float32Array(uint32Array.buffer);
    return float32Array[0];
}

export function byte2ToInt16(bytes: number[]): number {
    bytes = bytes.reverse()
    let uint8Array = new Uint8Array(bytes);
    let int16Array = new Int16Array(uint8Array.buffer);
    return int16Array[0];
}

export function byte4ToFloat32(bytes: number[]): number {
    bytes = bytes.reverse()
    let uint8Array = new Uint8Array(bytes);
    let float32Array = new Float32Array(uint8Array.buffer);
    return float32Array[0];
}

export function byte4ToInt32(bytes: number[]): number {
    bytes = bytes.reverse()
    let uint8Array = new Uint8Array(bytes);
    let int32Array = new Int32Array(uint8Array.buffer);
    return int32Array[0];
}

export function ChecksumBytesCore(bytes: number[]): number {
    let checksum = bytes[0];
    for(let i=1; i<bytes.length; i++) {
        checksum = checksum + bytes[i];
    }
    return checksum;
}

export function checksumBytes(bytes: number[]): number {
    return ChecksumBytesCore(bytes) & 0x7f;
}

export function checksumBytesPure(bytes: number[]): number {
    return ChecksumBytesCore(bytes) & 0xff;
}



// Text encoder polyfill
class TextEncoder {
    encode(string: string) {
        var octets = [];
        var length = string.length;
        var i = 0;
        while (i < length) {
          var codePoint = string.codePointAt(i) as number;
          var c = 0;
          var bits = 0;
          if (codePoint <= 0x0000007F) {
            c = 0;
            bits = 0x00;
          } else if (codePoint <= 0x000007FF) {
            c = 6;
            bits = 0xC0;
          } else if (codePoint <= 0x0000FFFF) {
            c = 12;
            bits = 0xE0;
          } else if (codePoint <= 0x001FFFFF) {
            c = 18;
            bits = 0xF0;
          }
          octets.push(bits | (codePoint >> c));
          c -= 6;
          while (c >= 0) {
            octets.push(0x80 | ((codePoint >> c) & 0x3F));
            c -= 6;
          }
          i += codePoint >= 0x10000 ? 2 : 1;
        }
        return octets;
    };
}

class TextDecoder {
    decode(octets: number[]) {
        var string = "";
        var i = 0;
        while (i < octets.length) {
          var octet = octets[i];
          var bytesNeeded = 0;
          var codePoint = 0;
          if (octet <= 0x7F) {
            bytesNeeded = 0;
            codePoint = octet & 0xFF;
          } else if (octet <= 0xDF) {
            bytesNeeded = 1;
            codePoint = octet & 0x1F;
          } else if (octet <= 0xEF) {
            bytesNeeded = 2;
            codePoint = octet & 0x0F;
          } else if (octet <= 0xF4) {
            bytesNeeded = 3;
            codePoint = octet & 0x07;
          }
          if (octets.length - i - bytesNeeded > 0) {
            var k = 0;
            while (k < bytesNeeded) {
              octet = octets[i + k + 1];
              codePoint = (codePoint << 6) | (octet & 0x3F);
              k += 1;
            }
          } else {
            codePoint = 0xFFFD;
            bytesNeeded = octets.length - i;
          }
          string += String.fromCodePoint(codePoint);
          i += bytesNeeded + 1;
        }
        return string
    };
}